use crate::generators;
use crate::processor;

#[cfg(debug_assertions)]
use std::fs;

/// Generator for HTML
pub struct HtmlGenerator {
    head: String,
    has_page_title: bool,
    header_count: usize,
    style_info: String,
}

/// Gets the default css file as a string
/// Note: in a debug build, it needs be in '../../assets/default_html_style.css'
/// relative to the current working directory.
#[cfg(debug_assertions)]
fn get_default_css() -> String {
    fs::read_to_string("../../assets/default_html_style.css").expect(
        "Could not find 'default_html_style.css',\
                 this file is required to be in the same directory for debug builds such as this!\
                 It would be a good idea to copy it from src/generators to here!",
    )
}

/// Gets the default css file as a string
/// Note: in a debug build, it needs be in '../../assets/default_html_style.css'
/// relative to the current working directory.
#[cfg(not(debug_assertions))]
fn get_default_css() -> String {
    String::from(include_str!("../../assets/default_html_style.css"))
}

impl HtmlGenerator {
    /// Creates a new HTML Generator
    pub fn new() -> HtmlGenerator {
        HtmlGenerator {
            head: String::new(),
            has_page_title: false,
            header_count: 0,
            style_info: get_default_css(),
        }
    }

    /// Creates a new html generator with a stylesheet.
    /// NOTE: is a TODO currently, is the same as `HtmlGenerator::new()`
    pub fn _new_with_style(_style: &str) -> HtmlGenerator {
        HtmlGenerator::new()
    }

    /// Adds some content to head (the content between the <head> tags)
    /// can be used for meta-info, and style info.
    pub fn add_to_head(&mut self, content: &str) {
        self.head.push_str(content)
    }

    /// Adds the title to the document
    /// Returns a header even if there are multiple titles.
    fn add_title(&mut self, title: &str) -> String {
        if !self.has_page_title {
            // adds title to head if not already
            self.add_to_head(format!("<title>{}</title>", title).as_str());
        }

        // adds to body
        format!("<h1 class=\"title\">{}</h1>", title)
    }

    /// adds a header to self.
    fn add_header(&mut self, header: &str, numbered: bool) -> String {
        match numbered {
            true => {
                self.header_count += 1;
                format!(
                    "<h2 class=\"header\">{}. {}</h2>",
                    self.header_count, header
                )
            }
            false => format!("<h2 class=\"header\">{}</h2>", header),
        }
    }
}

impl generators::Generator for HtmlGenerator {
    /// Formats a token into HTML
    fn format_token(&mut self, token: &processor::DocToken) -> String {
        match token {
            processor::DocToken::Title(content) => self.add_title(content.as_str()),
            processor::DocToken::Author(content) => {
                format!("<h3 class=\"author\">{content}</h3>")
            }
            processor::DocToken::Date(content) => {
                format!("<h4 class=\"date\">{content}</h4>")
            }

            processor::DocToken::Newline => String::from("<br/>"),
            processor::DocToken::Comment => String::new(),

            processor::DocToken::NHeader(content) => self.add_header(content.as_str(), true),
            processor::DocToken::Header(content) => self.add_header(content.as_str(), false),

            processor::DocToken::Bold(content) => format!("<b>{content}</b>"),
            processor::DocToken::Italic(content) => format!("<i>{content}</i>"),
            processor::DocToken::BoldItalic(content) => format!("<b><i>{content}</b></i>"),
            processor::DocToken::Text(content) => format!("<span>{content} </span>"),
        }
    }

    /// Generates html from DocToken Vector
    /// All html should be viewable in anything that can view HTML files
    fn generate(&mut self, tokens: &Vec<processor::DocToken>) -> String {
        // generate html
        let body: String = tokens
            .into_iter()
            .map(|token| self.format_token(token))
            .collect();

        // generates into a single string
        let head = &self.head;
        let style_info = &self.style_info;
        format!(include_str!("../../assets/html_template.html"), head, style_info, body)
    }
}
